import java.util.*;
import java.io.IOException;
import java.net.*;

public class Table extends Thread {

	// TODO Surround methods bidding and drawing with try...catch block to pass
	// the turn to next person if one disconnects during one's turn

	private int smallBlind;
	private int bigBlind;
	private PlayerThread[] playersThreads;
	private int initialCash;
	private static boolean isOn;
	private int maxPlayers;
	private Player[] players;
	private int pot;
	private List<Card> deck;
	private List<Card> spareDeck;
	private Random generator;
	private int whoseTurn;
	private int whosDealer;
	private int highestBet = 0;

	Table(int smallBlind, int bigBlind, int initialCash, int maxPlayers) {

		this.smallBlind = smallBlind;
		this.bigBlind = bigBlind;
		this.initialCash = initialCash;
		this.maxPlayers = maxPlayers;
		generator = new Random();
		pot = 0;
		isOn = false;

		players = new Player[maxPlayers];

		deck = new ArrayList<Card>(52);
		createDeck();

		spareDeck = new ArrayList<Card>(51);

		System.out.println("Table is ready");

		// TODO Add shutdown hook;

	}

	public void addPlayers(PlayerThread[] playersThreads) {
		this.playersThreads = playersThreads;

		for (int i = 0; i < maxPlayers; i++) {
			players[i] = new Player(initialCash, this);
		}

	}

	private void quit(String message) {
		for (int i = 0; i < maxPlayers; i++) {
			if (playersThreads[i] != null) {
				playersThreads[i].sendMessage(message);
				playersThreads[i].sendMessage("***Bye***");
				this.interrupt();
				playersThreads[i].close();
				isOn = false;
				System.exit(0);
			}

		}
	}

	private boolean charge(int playerId, int amount, String message) {

		if (playersThreads[playerId] == null)
			return false;

		try {
			players[playerId].bet(amount);
			if (players[playerId].getBet() == highestBet) {
				playersThreads[playerId].sendMessage("***You played call***");
				playersThreads[playerId].messageEveryone("***Player "
						+ playerId + " played call***");
				playersThreads[playerId].messageEveryone("~PLAYED " + playerId
						+ " 2");
				playersThreads[playerId].sendMessage("~CASH "
						+ players[playerId].getCash());
			} else {
				playersThreads[playerId].messageEveryone("***Player "
						+ playerId + " placed a bet " + amount + message
						+ "***");
				playersThreads[playerId].sendMessage("***You bet " + amount
						+ message + "***");
				playersThreads[playerId].messageEveryone("~PLAYED " + playerId
						+ " 1");
				playersThreads[playerId].sendMessage("~CASH "
						+ players[playerId].getCash());

			}
			this.messageEveryone("***Pot : " + pot + " ***");

			return true;

		} catch (BadugiException e) {

			if (amount == 0) {
				return false;
			}

			if (message.length() > 0) {
				playersThreads[playerId]
						.sendMessage("***After charging your account with "
								+ amount + " you would have no chips left***");
				playersThreads[playerId]
						.sendMessage("***You have to leave the game***");
				pot = pot + players[playerId].getCash();

				if (playersThreads[playerId] != null) {
					playersThreads[playerId].messageEveryone("***Player "
							+ playerId + " added "
							+ players[playerId].getCash() + " to the pot***");
					playersThreads[playerId].messageEveryone("***Pot : " + pot
							+ "***");
				}
				// TODO SEND CONTROL MESSAGE
				playersThreads[playerId].quit();
			} else {
				doAllIn(playerId);
			}

			return false;

		}
	}

	private void startGame() {

		// Game is now on
		isOn = true;

		// Assign dealer button;
		synchronized (this) {
			do {
				whosDealer = (whosDealer + 1) % maxPlayers;
			} while (playersThreads[whosDealer] == null);

		}

		synchronized (this) {
			playersThreads[whosDealer]
					.sendMessage("***You have the dealer button***");
		}

		// Charge small blind
		int playerCounter = 1;

		while ((!charge((whosDealer + playerCounter) % maxPlayers, smallBlind,
				" (small blind)"))
				&& (playerCounter % maxPlayers != (whosDealer + 1) % maxPlayers)) {
			playerCounter++;
		}

		// TODO If playercounter==maxPlayers-1 then game is over, because nobody
		// made a bet;
		// In case only two players are playing, one is the dealer and the other
		// HAS TO place smallBlind, because either he will place smallBlind or
		// he will place bigBlind (which is even bigger)

		// Charge big blind

		playerCounter = 1;

		while ((!charge((whosDealer + 1 + playerCounter) % maxPlayers,
				bigBlind, " (big blind)"))
				&& (playerCounter % maxPlayers != (whosDealer + 1) % maxPlayers)) {
			playerCounter++;
		}

		highestBet = bigBlind;
		// TODO Similiar improvement. Player, who placed small blind, can not
		// place big blind

		// Dealing the cards;

		for (int i = 0; i < maxPlayers; i++) {
			if (playersThreads[i] != null) {
				dealCards(i, 4);
				sendCards(i);
			}
		}

		boolean bidding = true;
		int i = 1;
		while (i <= 1) {
			if (bidding) {
				if (getActivePlayers(true) > 1) {
					messageEveryone("***Bidding " + i + "***");
					System.out.println("Bidding " + i);
					if (i == 1)
						whoseTurn = (whosDealer + 3) % maxPlayers;
					else
						whoseTurn = (whosDealer + 1) % maxPlayers;
					while (!players[whoseTurn].canBid()) {
						findNextPlayer();
					}
					bidding();
				}
				bidding = false;
			} else {

				if (getActivePlayers(false) > 1) {
					messageEveryone("***Drawing " + i + "***");
					System.out.println("Drawing " + i);
					while (!players[whoseTurn].canChangeCards()) {
						findNextPlayer();
					}
					drawing();
					bidding = true;
				}
				i++;
			}
			messageEveryone("~PHASE " + i);

		}

		if (getActivePlayers(true) > 1) {
			messageEveryone("***Bidding 4***");
			System.out.println("Bidding 4");
			if (i == 1)
				whoseTurn = (whosDealer + 3) % maxPlayers;
			else
				whoseTurn = (whosDealer + 1) % maxPlayers;
			while (!players[whoseTurn].canBid()) {
				findNextPlayer();
			}
			bidding();
		}

		results();
		cleanup();
	}

	public boolean gameOn() {
		return isOn;
	}

	private void dealCards(int playerId, int numberOfCards) {

		if (numberOfCards > deck.size()) {
			for (int i = deck.size() - 1; i >= 0; i++) {
				spareDeck.add(deck.get(i));
				deck.remove(i);
			}

			List<Card> temp = deck;
			deck = spareDeck;
			spareDeck = temp;
		}

		for (int i = 0; i < numberOfCards; i++) {
			int whichCard = generator.nextInt(deck.size());
			Card chosenCard = deck.get(whichCard);
			players[playerId].addCard(chosenCard);

			playersThreads[playerId].sendMessage("**You got a new card : "
					+ chosenCard.getValue() + " " + chosenCard.getSuit()
					+ " ***");

			deck.remove(whichCard);

		}

	}

	private void createDeck() {

		for (int i = 1; i <= 13; i++) {
			for (int j = 1; j <= 4; j++) {
				try {
					deck.add(new Card(i, j));
				} catch (BadugiException e) {
					System.out.println(e.getMessage());
				}
			}
		}

	}

	public void doAllIn(int playerId) {

		playersThreads[playerId].messageEveryone("***Player " + playerId
				+ " played all-in***");
		playersThreads[playerId].sendMessage("***You played all-in***");
		players[playerId].allIn();
		messageEveryone("***Pot : " + pot + "***");
		playersThreads[playerId].messageEveryone("~PLAYED " + playerId + " 1");
		playersThreads[playerId].sendMessage("~CASH "
				+ players[playerId].getCash());
	}

	public void doFold(int playerId) {
		playersThreads[playerId].messageEveryone("***Player " + playerId
				+ " played fold***");
		playersThreads[playerId].messageEveryone("~PLAYED " + playerId + " 1");
		playersThreads[playerId].sendMessage("***You played fold***");
		players[playerId].fold();
	}

	public void doCheck(int playerId) {

		playersThreads[playerId].messageEveryone("***Player " + playerId
				+ " played check***");
		playersThreads[playerId].messageEveryone("~PLAYED " + playerId + " 3");
		playersThreads[playerId].sendMessage("***You played check***");
	}

	public void run() {
		messageEveryone("~PARAMS " + maxPlayers + " " + initialCash);

		whosDealer = generator.nextInt(maxPlayers);
		do {
			startGame();
			messageEveryone("***New hand will start in 20 seconds***");
			try {
				synchronized (this) {
					wait(20000);
				}
				messageEveryone("~NEXT");

			} catch (InterruptedException e) {

			}
		} while (isOn);

	}

	public void addToPot(int amount) {
		this.pot = pot + amount;
	};

	private String getLine() {
		String line;

		synchronized (this) {
			try {
				playersThreads[whoseTurn].yourTurn();
				this.wait(60000);
			} catch (InterruptedException e) {
				e.printStackTrace();
				System.exit(0);
			}
		}
		;

		if (playersThreads[whoseTurn] != null) {
			line = playersThreads[whoseTurn].getLine();
			System.out.println("Got the line: " + line);
		} else {
			line = null;
		}

		if (line != null)
			return line;
		else
			return null;
	}

	private void bidding() {

		// firstPlayer is a player who was the last one to bid more than
		// highestBet of money in this bidding or was first to take an action;

		int firstPlayerId = whoseTurn;
		String line;
		String[] parts;
		boolean secondChance = false;

		do {

			if (getActivePlayers(true) == 1) {
				break;
			}

			System.out.println("Highest bet " + highestBet);
			playersThreads[whoseTurn].sendMessage("***Highest bet: "
					+ highestBet + " your bet: " + players[whoseTurn].getBet()
					+ "***");

			secondChance = false;

			line = getLine();

			if (line != null) {
				parts = line.split(" ");
				switch (parts[0]) {
				case "*A":
					doAllIn(whoseTurn);

					break;
				case "*F":
					doFold(whoseTurn);
					break;

				case "*C":
					if (!charge(whoseTurn,
							highestBet - players[whoseTurn].getBet(), "")) {
						playersThreads[whoseTurn]
								.sendMessage("***You can't play call!***");
						secondChance = true;
						continue;
					}
					break;
				case "*B":
					try {
						int bet = Integer.parseInt(parts[1]);

						if (bet + players[whoseTurn].getBet() < highestBet) {
							playersThreads[whoseTurn]
									.sendMessage("***You have to bet  at least "
											+ (highestBet - players[whoseTurn]
													.getBet()) + "!***");
							secondChance = true;
							continue;
						}

						if (!charge(whoseTurn, bet, "")) {
							playersThreads[whoseTurn]
									.sendMessage("***You can't bet 0!***");
							secondChance = true;
							continue;
						} else {
							if (players[whoseTurn].getBet() > highestBet) {
								highestBet = players[whoseTurn].getBet();
								firstPlayerId = whoseTurn;
							}
						}

					} catch (NumberFormatException e) {
						if (playersThreads[whoseTurn] != null) {
							playersThreads[whoseTurn]
									.sendMessage("***Wrong data***");
							secondChance = true;
							continue;
						} else {
							// TODO choose next player and put all the
							// disconnected
							// player's money in the pot
						}

					}
					break;

				case "*CH":
					if (highestBet == players[whoseTurn].getBet()) {
						doCheck(whoseTurn);
					} else {
						playersThreads[whoseTurn]
								.sendMessage("***You can't play check***");
						secondChance = true;
						continue;
					}
					break;
				default:
					secondChance = true;
					continue;
				}
			} else {
				playersThreads[whoseTurn] = null;
			}

			do {
				findNextPlayer();
			} while (whoseTurn != firstPlayerId
					&& !(players[whoseTurn].canBid())
					&& getActivePlayers(true) > 1);

		} while ((whoseTurn != firstPlayerId || secondChance)
				&& getActivePlayers(true) > 1);
		System.out.println("End of bidding " + secondChance);
	}

	private int getActivePlayers(boolean type) {
		int counter = 0;
		for (int i = 0; i < players.length; i++) {
			if (playersThreads[i] != null) {
				if (type && players[i].canBid()) {
					System.out.println("Player " + i + " can bid");
					counter++;
				} else if (!type && players[i].canChangeCards()) {
					System.out.println("Player " + i + " can draw");
					counter++;
				}
			}
		}
		System.out.println("Counter : " + counter);
		return counter;
	}

	private void findNextPlayer() {
		int playerCounter = 0;

		do {
			playerCounter++;
			whoseTurn = (whoseTurn + 1) % maxPlayers;
		} while ((playersThreads[whoseTurn] == null)
				&& (playerCounter < maxPlayers));
	}

	private void sendCards(int playerId) {
		Collections.sort(players[playerId].getHand().getCards());
		Hand copyHand = new Hand(players[playerId].getHand());

		copyHand = copyHand.evaluateBestHand(copyHand);
		int type = copyHand.getCards().size();
		int score = copyHand.getCard(type - 1).getValue();

		for (Card c : players[playerId].getHand().getCards()) {
			boolean isDifferent = true;

			for (Card c2 : copyHand.getCards()) {
				if (c.isSame(c2)) {
					isDifferent = false;
				}
			}

			if (isDifferent)
				copyHand.add(c);
		}

		players[playerId].setHand(copyHand);

		String cards = "";
		List<Card> currentCards = players[playerId].getHand().getCards();

		for (int i = 0; i < currentCards.size() - 1; i++) {
			cards = cards + "" + currentCards.get(i).getValue() + " "
					+ currentCards.get(i).getSuit();
			cards = cards + "|";
		}
		cards = cards + ""
				+ currentCards.get(currentCards.size() - 1).getValue() + " "
				+ currentCards.get(currentCards.size() - 1).getSuit();

		playersThreads[playerId].sendMessage("***Your cards: " + cards + "***");
		playersThreads[playerId].sendMessage("***Your type: " + type + "***");
		playersThreads[playerId].sendMessage("***Your score: " + score + "***");
		playersThreads[playerId].sendMessage("~HAND " + type + " " + score);

	}

	private void drawing() {
		boolean secondChance = false;
		int firstPlayerId = whoseTurn;

		do {
			secondChance = false;
			String line = getLine();
			String parts[] = line.split(" ");

			if (!parts[0].equals("*CH") || parts.length > 5 || parts.length < 1) {
				playersThreads[whoseTurn].sendMessage("***Wrong data***");
				secondChance = true;
				continue;
			} else {
				try {
					for (int i = 1; i < parts.length; i++) {
						int index = Integer.parseInt(parts[i]);
						spareDeck.add(players[whoseTurn].getCard(index));
						players[whoseTurn].removeCard(index);
						dealCards(whoseTurn, 1);
						sendCards(whoseTurn);

					}

				} catch (NumberFormatException e) {
					playersThreads[whoseTurn].sendMessage("***Wrong data***");
					secondChance = true;
					continue;
				}
			}

			do {
				findNextPlayer();
			} while (whoseTurn != firstPlayerId
					&& !(players[whoseTurn].canChangeCards())
					&& getActivePlayers(false) > 1);

			// System.out.println("FP " + firstPlayerId + " WT " + whoseTurn);

		} while ((secondChance || firstPlayerId != whoseTurn)
				&& getActivePlayers(false) > 1);

	}

	private void messageEveryone(String message) {
		for (int i = 0; i < maxPlayers; i++) {
			if (playersThreads[i] != null) {
				playersThreads[i].sendMessage(message);
			}
		}
	}

	private void cleanup() {
		highestBet = 0;

		// deck cleanup

		for (int i = spareDeck.size() - 1; i >= 0; i--) {
			deck.add(spareDeck.get(i));
			spareDeck.remove(i);
		}

		// players cleanup;
		for (int j = 0; j < maxPlayers; j++) {

			System.out.println("Cleanup: Player "+j);
			for (int i = 3; i >= 0; i--) {
				deck.add(players[j].getCard(i));
				players[j].getHand().remove(i);
			}

			if (players[j].getCash() > 0) {
				players[j].resetBet();
				players[j].resetStatus();
			} else {
				// remove players with no money left
				playersThreads[j].sendMessage("***You have no cash left***");
				playersThreads[j].quit();
			}

		}

	}

	private int findWinner() {

		boolean potentialDraw = false;

		int firstPlayerId = 0;
		int i = 0;
		while ((playersThreads[i] == null || players[i].canChangeCards() == false)
				&& i != (firstPlayerId-1)%maxPlayers) {
			i = (i + 1) % maxPlayers;
		}

		System.out.println("Starting coparision from... " + i);
		int winningId = i;
		System.out.println("Assuming the winner is ..." + i);
		firstPlayerId = i;

		do {

			do {
				i = (i + 1) % maxPlayers;
				System.out.println("I " + i);
			} while ((playersThreads[i] == null || players[i].canChangeCards()==false)
					&& i != winningId%maxPlayers);

			System.out.println("Teraz sprawdzimy i " +i);
			
			if (i != winningId) {
				messageEveryone("Comparing hands of player " + winningId
						+ " and " + i);
				int result = players[winningId].getHand().compareTo(
						players[i].getHand());
				messageEveryone("" + result);
				if (result < 0) {
					winningId = i;
					potentialDraw = false;
				} else if (result == 0) {
					potentialDraw = true;
				}
			}
		} while (i != winningId);

		if (potentialDraw)
			return -1;
		else
			return winningId;
	}

	private void results() {

		for (int i = 0; i < maxPlayers; i++)
			if (playersThreads[i] != null) {
				players[i].resetBet();
				messageEveryone("***Player " + i + "'s cards :***");
				Hand currentHandCopy = new Hand(players[i].getHand());
				currentHandCopy = currentHandCopy
						.evaluateBestHand(currentHandCopy);
				playersThreads[i].messageEveryone("~RESULT " + i + " "
						+ currentHandCopy.getCards().size());
				for (int j = 0; j < 4; j++) {
					messageEveryone("" + players[i].getCard(j).getValue() + " "
							+ players[i].getCard(j).getSuit());
				}
			}

		int winner = findWinner();
		if (winner == -1) {
			messageEveryone("***Its a draw!***");
			pot = 0;
		} else {
			messageEveryone("***Player " + winner + " wins!***");
			players[winner].addCash(pot);
			pot = 0;
		}
	}

	public void countPlayers() {
		synchronized (this) {
			int counter = 0;
			for (int i = 0; i < maxPlayers; i++) {
				if (playersThreads[i] != null)
					counter++;
			}
			if (counter < 2)
				quit("***Less than 2 players left***");
		}
	}

}
